Skip to content

feature/cp-10963-add-bridge-function-in-mobile-sdk-to-send-subscription#331

Merged
gbhrdt-cp merged 3 commits intomasterfrom
feature/cp-10963-add-bridge-function-in-mobile-sdk-to-send-subscription
Mar 11, 2026
Merged

feature/cp-10963-add-bridge-function-in-mobile-sdk-to-send-subscription#331
gbhrdt-cp merged 3 commits intomasterfrom
feature/cp-10963-add-bridge-function-in-mobile-sdk-to-send-subscription

Conversation

@unnaticleverpush
Copy link
Contributor

@unnaticleverpush unnaticleverpush commented Feb 24, 2026

Added getSubscriptionContextRequest() in the JS bridge; injected Promise-based getSubscriptionContext in both HTML-block and fullscreen banner scripts in AppBannerCarouselAdapter and InboxDetailBannerCarouselAdapter.


Note

Medium Risk
Touches WebView JS injection and @JavascriptInterface surfaces; mistakes in JSON escaping or bridge wiring could break HTML banners or expose unexpected JS-bridge behavior.

Overview
Adds a new JS-bridge API for banner WebViews to retrieve the current subscription context (subscriptionId, channelId). Both AppBannerCarouselAdapter and InboxDetailBannerCarouselAdapter now inject CleverPush.subscriptionContext on load and provide a Promise-based CleverPush.getSubscriptionContext() that is resolved via a new @JavascriptInterface method getSubscriptionContextRequest().

To support async callbacks, CleverpushInterface now receives the owning WebView, and subscription context JSON generation is centralized in getSubscriptionContextJson() (null-safe, iOS-compatible).

Written by Cursor Bugbot for commit a75daae. This will update automatically on new commits. Configure here.


Summary by cubic

Adds a JS bridge to send subscription context (subscriptionId, channelId) to banner WebViews and exposes a Promise-based CleverPush.getSubscriptionContext, with subscriptionContext preloaded. Fulfills CP-10963 and mirrors iOS behavior.

  • New Features

    • Added @JavascriptInterface getSubscriptionContextRequest() to resolve the Promise with JSON { subscriptionId, channelId } (null when missing), with safe JSON escaping.
    • Injected CleverPush.getSubscriptionContext and prefilled CleverPush.subscriptionContext in both AppBannerCarouselAdapter and InboxDetailBannerCarouselAdapter (HTML-block and fullscreen banners).
  • Refactors

    • CleverpushInterface now holds a WebView for UI-thread callbacks; moved copyToClipboard into the bridge and centralized context JSON via getSubscriptionContextJson; removed an unused import.

Written for commit a75daae. Summary will update on new commits.

Added getSubscriptionContextRequest() in the JS bridge; injected Promise-based getSubscriptionContext in both HTML-block and fullscreen banner scripts in AppBannerCarouselAdapter and InboxDetailBannerCarouselAdapter.
@unnaticleverpush unnaticleverpush self-assigned this Feb 24, 2026
Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 2 files

);
String contextJson = getSubscriptionContextJson();
webView.evaluateJavascript(
"if (typeof CleverPush !== 'undefined') { CleverPush.subscriptionContext = " + contextJson + "; }",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

XSS vulnerability from unescaped JSON in JavaScript

High Severity

The raw JSON from getSubscriptionContextJson() is directly concatenated into JavaScript code without proper escaping. If subscription IDs or channel IDs contain characters like </script>, <, >, or /, they could break out of the JavaScript context and enable XSS attacks. While Gson.toJson() handles basic JSON escaping, it doesn't prevent script injection when the JSON is embedded directly in JavaScript code.

Additional Locations (2)

Fix in Cursor Fix in Web

if (webView == null) return;
String contextJson = getSubscriptionContextJson();
String escaped = contextJson.replace("\\", "\\\\").replace("\"", "\\\"")
.replace("\r", "\\r").replace("\n", "\\n");
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incomplete character escaping for JavaScript injection

Medium Severity

The manual escaping only handles backslash, quote, carriage return, and newline characters, but omits tab (\t) and other control characters. If the JSON contains unescaped tabs or control characters, they could cause JavaScript syntax errors when the string is parsed by JSON.parse(), breaking the Promise resolution and preventing subscription context from being delivered.

Additional Locations (1)

Fix in Cursor Fix in Web


public CleverpushInterface(WebView webView) {
this.webView = webView;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Memory leak from circular WebView reference

Medium Severity

The CleverpushInterface stores a reference to the WebView that it's added to via addJavascriptInterface. This creates a circular reference: WebView holds the interface, and the interface holds the WebView. This prevents garbage collection and causes memory leaks when WebViews are destroyed, particularly in RecyclerView scenarios where views are recycled.

Additional Locations (1)

Fix in Cursor Fix in Web

});

webView.loadUrl(block.getUrl());
webView.addJavascriptInterface(new CleverpushInterface(), "CleverPush");
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Promise never rejects leading to unhandled hangs

Medium Severity

The CleverPush.getSubscriptionContext Promise only accepts a resolve callback, never a reject. If getSubscriptionContextRequest returns early due to null webView (line 996) or if any other error prevents resolution, the Promise hangs indefinitely. JavaScript callers using await or .then() will never receive a response, blocking execution.

Additional Locations (1)

Fix in Cursor Fix in Web


webView.loadUrl(block.getUrl());
webView.addJavascriptInterface(new CleverpushInterface(), "CleverPush");
webView.addJavascriptInterface(new CleverpushInterface(webView), "CleverPush");
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JavaScript interface added after URL load

High Severity

The addJavascriptInterface is called after loadUrl, but according to Android WebView documentation, adding an interface is not reflected on the JavaScript side until the next page load. This means the CleverPush object won't be available when the page loads or in onPageFinished, causing JavaScript errors when code tries to call CleverPush.getSubscriptionContextRequest() and other interface methods.

Additional Locations (1)

Fix in Cursor Fix in Web

" return new Promise(function(resolve) {\n" +
" window.CleverPush._resolveSubscriptionContext = resolve;\n" +
" CleverPush.getSubscriptionContextRequest();\n" +
" });\n" +
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Concurrent Promise calls overwrite resolver

Medium Severity

If JavaScript calls CleverPush.getSubscriptionContext() multiple times concurrently, the second call overwrites window.CleverPush._resolveSubscriptionContext, causing the first Promise to never resolve. Only the most recent call's Promise will resolve; all previous Promises hang indefinitely, leaking memory and blocking any code waiting on those Promises.

Additional Locations (1)

Fix in Cursor Fix in Web

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

Map<String, Object> empty = new LinkedHashMap<>();
empty.put("subscriptionId", (Object) null);
empty.put("channelId", (Object) null);
return new Gson().toJson(empty);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gson silently drops null values from subscription context JSON

High Severity

new Gson().toJson(context) silently omits null map entries because Gson's default is serializeNulls = false. When getSubscriptionId or getChannelId returns null, the output becomes {} instead of the intended {"subscriptionId":null,"channelId":null}. The catch block is equally broken — it explicitly puts null values, but Gson strips them. This contradicts the stated goal of matching iOS behavior with null for missing values. Using new GsonBuilder().serializeNulls().create() instead of new Gson() would fix this.

Additional Locations (1)

Fix in Cursor Fix in Web

@gbhrdt-cp gbhrdt-cp merged commit 869648b into master Mar 11, 2026
4 of 6 checks passed
@gbhrdt-cp gbhrdt-cp deleted the feature/cp-10963-add-bridge-function-in-mobile-sdk-to-send-subscription branch March 11, 2026 08:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants